home *** CD-ROM | disk | FTP | other *** search
/ Personal Computer World 2008 February / PCWFEB08.iso / Software / Resources / Developers / XAMPP 1.5.4 / Windows installer / xampp-win32-1.5.4-installer.exe / xampp / php / pear / CodeGen / XmlParser.php < prev   
Encoding:
PHP Script  |  2006-04-07  |  17.5 KB  |  600 lines

  1. <?php
  2. /**
  3.  * Yet another XML parsing class 
  4.  *
  5.  * PHP versions 5
  6.  *
  7.  * LICENSE: This source file is subject to version 3.0 of the PHP license
  8.  * that is available through the world-wide-web at the following URI:
  9.  * http://www.php.net/license/3_0.txt.  If you did not receive a copy of
  10.  * the PHP License and are unable to obtain it through the web, please
  11.  * send a note to license@php.net so we can mail you a copy immediately.
  12.  *
  13.  * @category   Tools and Utilities
  14.  * @package    CodeGen
  15.  * @author     Hartmut Holzgraefe <hartmut@php.net>
  16.  * @copyright  2005 Hartmut Holzgraefe
  17.  * @license    http://www.php.net/license/3_0.txt  PHP License 3.0
  18.  * @version    CVS: $Id: XmlParser.php,v 1.12 2006/02/04 19:17:32 hholzgra Exp $
  19.  * @link       http://pear.php.net/package/CodeGen
  20.  */
  21.  
  22. /**
  23.  * Yet another XML parsing class 
  24.  *
  25.  * This is similar to the "func" mode of XML_Parser but it borrows
  26.  * some concepts from DSSSL.
  27.  * The tag handler method to call is not only determined by the tag
  28.  * name but also potentially by the name of its parent tags, and the
  29.  * most specific handler method (that is the one including the
  30.  * maximum number of matching parent tags in its name) wins.
  31.  * This way it is possible to have e.g. tagstart_name as a general
  32.  * handler for a <name> tag while tagstart_function_name handles the
  33.  * more special case of a <name> tag within a <function> tag.
  34.  * Character data within a tag is collected and passed to the end
  35.  * tag handler.
  36.  * Tag names and attributes are managed using stack arrays.
  37.  * Attributes are not only passed to both the start and end tag 
  38.  * handlers.
  39.  *
  40.  * @category   Tools and Utilities
  41.  * @package    CodeGen
  42.  * @author     Hartmut Holzgraefe <hartmut@php.net>
  43.  * @copyright  2005 Hartmut Holzgraefe
  44.  * @license    http://www.php.net/license/3_0.txt  PHP License 3.0
  45.  * @version    Release: @package_version@
  46.  * @link       http://pear.php.net/package/CodeGen
  47.  */
  48.  
  49.     class CodeGen_XmlParser 
  50.     {
  51.         /**
  52.          * XML parser resource
  53.          *
  54.          * @var resource
  55.          */
  56.         protected $parser = NULL;
  57.  
  58.         /**
  59.          * Parser stack for <include> management
  60.          *
  61.          * @var array
  62.          */
  63.         protected $parserStack = array();
  64.  
  65.         /**
  66.          * We collect cData in here
  67.          *
  68.          * @var    string
  69.          */
  70.         protected $data = "";
  71.  
  72.         /**
  73.          * We also try to remember where cData started
  74.          *
  75.          * @access private
  76.          * @var    int
  77.          */
  78.         protected $dataLine = 0;
  79.  
  80.         /** 
  81.          * We maintain the current tag nesting structure here
  82.          *
  83.          * @access private
  84.          * @var    array
  85.          */
  86.         protected $tagStack = array();
  87.  
  88.         /** 
  89.          * We keep track of tag attributes so that we can also provide them to the end tag handlers
  90.          *
  91.          * @access private
  92.          * @var    array
  93.          */
  94.         protected $attrStack = array();
  95.  
  96.         /**
  97.          * There is no clean way to terminate parsing from within a handler ...
  98.          *
  99.          * @access private
  100.          * @var    bool
  101.          */
  102.         protected $error = false;
  103.  
  104.         /**
  105.          * Input Filename
  106.          * 
  107.          * @access public
  108.          * @var    string
  109.          */
  110.         protected $filename = false;
  111.  
  112.         /**
  113.          * Input stream
  114.          *
  115.          * @access public
  116.          * @var    resource
  117.          */
  118.         protected $fp = null;
  119.  
  120.         /**
  121.          * Verbatim indicator
  122.          *
  123.          * @access public
  124.          * @var    string
  125.          */
  126.         protected $verbatim = false;
  127.  
  128.         /**
  129.          * Verbatim taglevel depth
  130.          *
  131.          * @access public
  132.          * @var string
  133.          */
  134.         protected $verbatimDepth = 0;
  135.  
  136.         /**
  137.          * The constructor 
  138.          *
  139.          * @access public
  140.          */
  141.         function __construct()
  142.         { 
  143.             $this->parser = $this->newParser();
  144.         }
  145.  
  146.         /**
  147.          * Create a new SAX parser and associate it with this XmlParser instance
  148.          *
  149.          * @void
  150.          */
  151.         private function newParser()
  152.         {
  153.             $parser = @xml_parser_create_ns(null, ' ');
  154.  
  155.             if (is_resource($parser)) {
  156.                 xml_parser_set_option($parser, XML_OPTION_CASE_FOLDING, false);
  157.  
  158.                 xml_set_object($parser, $this);
  159.                 xml_set_element_handler($parser, 'startHandler', 'endHandler');
  160.                 xml_set_character_data_handler($parser, 'cDataHandler');
  161.                 xml_set_external_entity_ref_handler($parser, 'extEntityHandler');
  162.                 xml_set_processing_instruction_handler($parser, 'piHandler');
  163.             }
  164.  
  165.             return $parser;
  166.         }
  167.  
  168.         /**
  169.          * Push current SAX parser instance to the parser stack
  170.          *
  171.          * @void
  172.          */
  173.         private function pushParser()
  174.         {
  175.             if ($this->parser) {
  176.                 $entry = array($this->parser, $this->filename, $this->fp);    
  177.                 array_push($this->parserStack, $entry);
  178.             }
  179.         }
  180.  
  181.         /**
  182.          * Replace current SAX parser with one popped from the parser stack
  183.          *
  184.          * @void
  185.          */
  186.         function popParser()
  187.         {
  188.             xml_parser_free($this->parser);
  189.             list($this->parser, $this->filename, $this->fp) = array_pop($this->parserStack);
  190.         }
  191.  
  192.         /**
  193.          * Generate current parse position as string for error messages
  194.          *
  195.          * @void
  196.          * @returns string
  197.          */
  198.         private function posString()
  199.         {
  200.              return "in {$this->filename} on line ".
  201.                  xml_get_current_line_number($this->parser).
  202.                  ":".
  203.                  xml_get_current_column_number($this->parser);
  204.         }
  205.         
  206.         /**
  207.          * Create a new SAX parser to parse an external entity reference
  208.          *
  209.          * @void
  210.          */
  211.         private function extEntityHandler($parser, $openEntityNames, $base, $systemId, $publicId) {
  212.             $this->pushParser();
  213.             $this->parser = $this->newParser();
  214.             $stat = $this->setInputFile($systemId);
  215.             if ($stat) {
  216.                 $this->parse();
  217.             } else {
  218.                 $this->error = PEAR::raiseError("Can't open system entity file '$systemId' ".$this->posString());
  219.             }
  220.             $this->popParser();
  221.             return;
  222.         }
  223.  
  224.         /**
  225.          * Set file to parse
  226.          *
  227.          * @access public
  228.          * @param string
  229.          */
  230.         public function setInputFile($filename) 
  231.         {
  232.             $this->filename = $filename;
  233.             
  234.             $this->fp = @fopen($filename, "r");
  235.  
  236.             return is_resource($this->fp);
  237.         }
  238.  
  239.         /**
  240.          * Perform the actual parsing
  241.          *
  242.          * @return boolean true on success
  243.          */
  244.         public function parse() 
  245.         {
  246.             if (!is_resource($this->parser)) {
  247.                 return PEAR::raiseError("Can't create XML parser");
  248.             }
  249.  
  250.             if (!is_resource($this->fp)) {
  251.                 return PEAR::raiseError("No valid input file");
  252.             }
  253.  
  254.  
  255.             while (($data = fread($this->fp, 4096))) {
  256.                 if (!xml_parse($this->parser, $data, feof($this->fp))) {
  257.                     $this->error = PEAR::RaiseError(xml_error_string(xml_get_error_code($this->parser))." ".$this->posString());
  258.                 }
  259.                 if ($this->error) {
  260.                     return $this->error;
  261.                 }
  262.             }
  263.  
  264.             $stat = $this->error ? $this->error : true;
  265.  
  266.             return $stat;
  267.         }
  268.  
  269.         /**
  270.          * Start verbatim mode
  271.          *
  272.          */
  273.         protected function verbatim()
  274.         {
  275.             $this->verbatim = true;
  276.             $this->verbatimDepth = 1;
  277.         }
  278.  
  279.         /**
  280.          * Try to find best matching tag handler for current tag nesting
  281.          * 
  282.          * @access private
  283.          * @param  string  handler method prefix
  284.          * @return string  hndler method name or false if no handler found
  285.          */
  286.         private function findHandler($prefix)
  287.         {
  288.             for ($tags = $this->tagStack; count($tags); array_shift($tags)) {
  289.                 $method = "{$prefix}_".join("_", $tags);
  290.                 if (method_exists($this, $method)) {
  291.                     return $method;
  292.                 }
  293.             }
  294.  
  295.             return false;
  296.         }
  297.  
  298.  
  299.         /**
  300.          * Try to find a tagstart handler for this tag and call it
  301.          *
  302.          * @access private
  303.          * @param  resource internal parser handle
  304.          * @param  string   tag name
  305.          * @param  array    tag attributes         
  306.          */
  307.         private function startHandler($XmlParser, $fulltag, $attr)
  308.         {
  309.             if ($this->error) return;
  310.  
  311.             $pos = strrpos($fulltag, " ");
  312.             
  313.             $ns  = $pos ? substr($fulltag, 0, $pos)  : "";
  314.             $tag = $pos ? substr($fulltag, $pos + 1) : $fulltag;
  315.  
  316.             // XInclude handling
  317.             if ($ns === "http://www.w3.org/2001/XInclude") {
  318.                 // TODO better error checking
  319.                 if ($tag === "include") {
  320.                     $path = isset($attr['href']) ? $attr['href'] : $attr['http://www.w3.org/2001/XInclude href'];
  321.  
  322.                     if (isset($attr["parse"]) && $attr["parse"] == "text") {
  323.                         if (is_readable($path)) {
  324.                             $data = file_get_contents($path);
  325.                             $this->cDataHandler($XmlParser, $data);
  326.                         } else {
  327.                             $this->error = PEAR::raiseError("Can't open XInclude file '$path' ".$this->posString());
  328.                         }
  329.                     } else {
  330.                         $this->pushParser();
  331.                         $this->parser = $this->newParser();
  332.                         $stat = $this->setInputFile($path);
  333.                         if ($stat) {
  334.                             $this->parse();
  335.                         } else {
  336.                             $this->error = PEAR::raiseError("Can't open XInclude file '$path' ".$this->posString());
  337.                         }
  338.                         $this->popParser();
  339.                     }
  340.                 }
  341.                 
  342.                 return;
  343.             }
  344.  
  345.             // this *has* to be done *after* XInclude processing !!!
  346.             array_push($this->tagStack,  $tag);
  347.             array_push($this->attrStack, $attr);
  348.  
  349.             if ($this->verbatim) {
  350.                 $this->verbatimDepth++;
  351.                 $this->data .= "<$tag";
  352.                 foreach ($attr as $key => $value) {
  353.                     $this->data .= " $key='$value'";
  354.                 }
  355.                 $this->data .= ">";
  356.                 return;
  357.             }
  358.  
  359.             $this->data = "";
  360.             $this->dataLine = 0;
  361.  
  362.             $method = $this->findHandler("tagstart");
  363.             if ($method) {
  364.                 $err = $this->$method($attr);
  365.                 if (PEAR::isError($err)) {
  366.                     $this->error = $err;
  367.                     $this->error->addUserInfo($this->posString());
  368.                 }
  369.             } else if (!$this->findHandler("tagend")) {
  370.                 $this->error = PEAR::raiseError("no matching tag handler for ".join(":",$this->tagStack));
  371.                 $this->error->addUserInfo($this->posString());              
  372.             }
  373.         }
  374.  
  375.         /**
  376.          * Try to find a tagend handler for this tag and call it
  377.          *
  378.          * @access private
  379.          * @param  resource internal parser handle
  380.          * @param  string   tag name
  381.          */
  382.         private function endHandler($XmlParser, $fulltag)
  383.         {
  384.             if ($this->error) return;
  385.  
  386.             $pos = strrpos($fulltag, " ");
  387.             
  388.             $ns  = $pos ? substr($fulltag, 0, $pos)  : "";
  389.             $tag = $pos ? substr($fulltag, $pos + 1) : $fulltag;
  390.  
  391.             // XInclude handling
  392.             if ($ns === "http://www.w3.org/2001/XInclude") {
  393.                 return;
  394.             }
  395.  
  396.             // this *has* to be done *before* popping the tag stack!!!
  397.             $method = $this->findHandler("tagend");
  398.  
  399.             $oldtag = array_pop($this->tagStack);
  400.             $attr   = array_pop($this->attrStack);
  401.  
  402.             if ($this->verbatim) {
  403.                 if (--$this->verbatimDepth > 0) {
  404.                     $this->data .= "</$tag>";
  405.                     return;
  406.                 } else { 
  407.                     $this->verbatim = false;
  408.                 }               
  409.             }
  410.  
  411.             if ($method) {
  412.                 $err = $this->$method($attr, $this->data, $this->dataLine, $this->filename);
  413.                 if (PEAR::isError($err)) {
  414.                     $this->error = $err;
  415.                     $this->error->addUserInfo($this->posString());                                   
  416.                 }
  417.             }
  418.             $this->data = "";
  419.             $this->dataLine = 0;
  420.         }
  421.  
  422.  
  423.         /**
  424.          * Just collect cData for later use in tag end handlers
  425.          *
  426.          * @access private
  427.          * @param  resource internal parser handle
  428.          * @param  string   cData to collect
  429.          */
  430.         private function cDataHandler($XmlParser, $data)
  431.         {
  432.             $this->data.= $data;
  433.             if (!$this->dataLine) {
  434.                 $this->dataLine = xml_get_current_line_number($XmlParser);
  435.             }
  436.         }
  437.  
  438.         /**
  439.          * Delegate processing instructions
  440.          *
  441.          * @access private
  442.          * @param  resource internal parser handle
  443.          * @param  string   PI name
  444.          * @param  string   PI content data
  445.          */
  446.         private function piHandler($XmlParser, $name, $data)
  447.         {
  448.             $methodName = $name."PiHandler";
  449.  
  450.             if (method_exists($this, $methodName)) {
  451.                 $this->$methodName($XmlParser, $data);
  452.             } else {
  453.                 $this->error = PEAR::raiseError("unknown processing instruction '<$name'");
  454.             }
  455.         }
  456.  
  457.         /**
  458.          * Tread <?data PI sections like <![CDATA[
  459.          *
  460.          * @access private
  461.          * @param  resource internal parser handle
  462.          * @param  string   cData to collect
  463.          */
  464.         private function dataPiHandler($XmlParser, $data) 
  465.         {
  466.             $this->cDataHandler($XmlParser, $data);
  467.         }
  468.  
  469.         /**
  470.          * A helper stack for collecting stuff 
  471.          *
  472.          * @access private
  473.          * @var    array
  474.          */
  475.         protected $helperStack = array();
  476.  
  477.         /**
  478.          * The current helper (top of stack) 
  479.          *
  480.          * @access private
  481.          * @var    mixed
  482.          */
  483.         protected $helper = false;
  484.  
  485.         /**
  486.          * The previous helper (top-1 of stack) 
  487.          *
  488.          * @access private
  489.          * @var    mixed
  490.          */
  491.         protected $helperPrev = false;
  492.         
  493.         /**
  494.          * Push something on the helper stack
  495.          *
  496.          * @access private
  497.          * @param mixed
  498.          */
  499.         protected function pushHelper($helper)
  500.         {
  501.             array_push($this->helperStack, $this->helper);
  502.             $this->helperPrev = $this->helper;
  503.             $this->helper = $helper;
  504.         }
  505.  
  506.  
  507.         /**
  508.          * Pop something from the helper stack
  509.          *
  510.          * @access private
  511.          */
  512.         protected function popHelper()
  513.         {
  514.             // TODO add optional expectedType parameter?
  515.  
  516.             $oldHelper = $this->helper;
  517.  
  518.             $this->helper = array_pop($this->helperStack);
  519.             if (count($this->helperStack)) {
  520.                 end($this->helperStack);
  521.                 $this->helperPrev = current($this->helperStack); 
  522.             } else {
  523.                 $this->helperPrev = false;
  524.             }
  525.  
  526.             return $oldHelper;
  527.         }
  528.  
  529.         
  530.         /**
  531.          * accept various truth values
  532.          *
  533.          * @access public
  534.          * @param  mixed
  535.          * @return bool
  536.          */
  537.         protected function toBool($arg)
  538.         {
  539.             if (is_bool($arg)) {
  540.                 return $arg;
  541.             }
  542.  
  543.             if (is_numeric($arg)) {
  544.                 return ($arg != 0);
  545.             }
  546.  
  547.             if (is_string($arg)) {
  548.                 switch (strtolower($arg)) {
  549.                 case 'on':
  550.                 case 'yes':
  551.                 case 'true':
  552.                     return true;
  553.                 default:
  554.                     return false;
  555.                 }
  556.             }
  557.                 
  558.             return false;
  559.         }
  560.  
  561.  
  562.         /**
  563.          * Check attributes
  564.          *
  565.          * @param array   actual attribute/value pairs
  566.          * @param array  optinal attribute names with default values
  567.          * @param array required attribute names
  568.          */
  569.         protected function checkAttributes(&$attr, $optional, $required = array())
  570.         {
  571.             // check for missing required attributes
  572.             foreach ($required as $key) {
  573.               if (!isset($attr[$key])) {
  574.                 return PEAR::raiseError("required attribute '$key' missing in <".end($this->tagStack)."> ");
  575.               }
  576.             }
  577.  
  578.             // add defaults for missing optional arguments
  579.             foreach ($optional as $key => $value) {
  580.               if (is_numeric($key)) {
  581.                 $key   = $value;
  582.                 $value = null;
  583.               }
  584.               if (!isset($attr[$key])) {
  585.                 $attr[$key] = $value;
  586.               }
  587.             }
  588.  
  589.             // check for unknown attributes
  590.             foreach ($attr as $key => $value) {
  591.                 if (!in_array($key, $required) && !in_array($key, $optional)) {
  592.                     return PEAR::raiseError("'$key' is not a valid attribute for <".end($this->tagStack)."> ");
  593.                 }
  594.             }
  595.             return true;
  596.         }
  597.     }
  598.  
  599. ?>
  600.